/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.appspot.trafficando.infrastructure.aspect;

import com.appspot.trafficando.domain.EventProvider;
import java.io.Serializable;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;

/**
 *
 * @author Domenico Maria Giffone
 */
public class CachingAspect extends AbstractOrderedAspect {

    private Log log = LogFactory.getLog(getClass());
    private CacheManager cacheManager;

    /**
     * @param cacheManager
     *
     * the cacheManager to set
     */
    public void setCacheManager(CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

    public Object doCache(ProceedingJoinPoint jp) throws Throwable {
        final Signature signature = jp.getSignature();
        final String targetMethod = signature.getDeclaringTypeName() + "." + signature.getName();
        if (log.isDebugEnabled()) {
            log.debug("Primo tentativo di ricerca nella cache senza argomenti.");
        }
        Cache cache = cacheManager.getCache(targetMethod);
        String cacheKey = targetMethod;
        boolean methodInvocationProceed = false;
        boolean methodInvocationCache = false;
        Object methodReturn = null;
        if (cache == null) {
            if (log.isDebugEnabled()) {
                log.debug("Nessun cache presente per il metodo " + targetMethod);
            }
        }
        if (cache != null) {
            if (log.isDebugEnabled()) {
                log.debug("Cache presente: " + cache.getName());
            }
            cacheKey = getSpecificKey(jp, targetMethod);
            if (log.isDebugEnabled()) {
                log.debug("Tentativo di estrazione dalla cache della chiave: " + cacheKey);
            }
            String remote = "";
            //Try local cache lookup
            Element cacheElement = cache.get(cacheKey);
            // If element is missing try distributed cache lookup
            if (null == cacheElement) {
                cacheElement = cache.getWithLoader(cacheKey, null, null);
                remote = "remota";
            }
            // Then invoke method
            if (cacheElement == null) {
                methodInvocationProceed = true;
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("Chiave presente nella cache " + remote);
                }
                methodReturn = (cacheElement.isSerializable()) ? cacheElement.getValue() : cacheElement.getObjectValue();
                if (log.isInfoEnabled()) {
                    log.info("Restituzione del valore associato alla chiave dalla cache.");
                }
            }
            methodInvocationCache = true;
        }
        if (cache == null || methodInvocationProceed) {
            if (log.isDebugEnabled()) {
                log.debug("Elemento assente dalla cache. Procedo con l'invocazione del metodo originale.");
            }
            methodReturn = jp.proceed();
            if (methodInvocationCache) {
                final Element newCacheElement = (methodReturn instanceof Serializable) ? new Element(cacheKey, (Serializable) methodReturn) : new Element(cacheKey, methodReturn);
                cache.put(newCacheElement);
                if (log.isDebugEnabled()) {
                    log.debug("Inserimento del valore nella cache.");
                }
            } else {
                if (log.isDebugEnabled()) {
                    log.warn("Restituisco il risultato senza inserirlo in cache!");
                    log.warn("Verificare la configurazione della cache o correggere il pointcut!");
                }
            }
        }
        return methodReturn;
    }

    private String getSpecificKey(ProceedingJoinPoint jp, final String targetMethod) {
        String cacheKey = targetMethod;
        final Object[] methodArgs = jp.getArgs();
        final Object target = jp.getTarget();
        if (methodArgs != null) {
            if (log.isDebugEnabled()) {
                log.debug("Invocazione con argomenti: Costruzione della chiave di ricerca a partire dagli argomenti.");
            }
            final StringBuffer keyBuilder = new StringBuffer(targetMethod);
            keyBuilder.append("(");
            for (int i = 0; i < methodArgs.length; i++) {
                keyBuilder.append(methodArgs[i].toString());
                if (i + 1 != methodArgs.length) {
                    keyBuilder.append(",");
                }
            }
            keyBuilder.append(")");
            cacheKey = keyBuilder.toString();
        }
        if (target != null && target instanceof EventProvider) {
            EventProvider ep = (EventProvider) target;
            cacheKey = ep.getName();
        }
        return cacheKey;
    }
}
